配置管理
最佳实践思想
- 区分必选值与可选择
- 配置文件与选项解耦
- 避免复杂
- 简单化, 除了必选的值, 默认值应该是科学的, 合理的, 最佳实践的值
- 防御编程, 防止其他人乱改和运行时修改配置, 如果用户手一抖, 一秒钟填写成1分钟, 对不合理的配置直接panic
- 配置版本和应用对齐, 回滚应用时, 应用和配置文件应该一一对应, 一起回滚, 在配置中心或者k8s配置
最佳实践
proto + 配置语言(json/yaml/toml)
proto可以进行高亮 yaml编写配置就可以与proto进行解耦
演进
函数配置
防止在运行时修改DailOption
配置
优点:
- 告诉你可选和必选值
- 函数内方便使用默认值
扩展:
如果要扩展, 必须新增函数, 例如
DailDatabase
等才可以扩展
type DailOption struct {
// dailOption, 小写, 意味着没有任何人有修改里面的值
f func(*dailOption)
}
func Dial(network, address string, options ...DailOption) (Conn, error){
// 默认值
do := dailOption {
dial: net.Dial,
}
// 如果没有传递options, 也不会造成影响
for _, option := range options {
option.f(&do)
}
}
或者:
type DailOption func(*dailOption)
func Dial(network, address string, options ...DailOption) (Conn, error){
do := dailOption {
dial: net.Dial,
}
for _, option := range options {
option(&do)
}
}
函数配置, 包含返回值
用于单元测试等, 需要切换参数配置时 改之前与改之后
type option func(f *Foo) option
func Verbosity(v int) option {
return func(f *Foo) option {
prev := f.verbostiy
f.verbostiy = v
return Verbosity(prev)
}
}
func DoSomethingVerbosity(foo *Foo, verbosity int) {
prev := foo.Option(pkg.Verbosity(verbostiy))
defer foo.Option(prev)
}
包含interface的函数配置
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*SayHelloReply, error)
}
type CallOption interface {
before(*callInfo) error
after(*callInfo)
}
// EmptyCallOption 不改变 CallCallOption
type EmptyCallOption struct{}
// 对原有的grpc.CallOption进行扩展
type TimeoutCallOption struct {
grpc.EmptyCallOption
Timeout time.Duration
}
其它配置的缺点
传统结构体(不加指针)
缺点: 不加指针是copy该结构体, 无法使用默认值, 以及0值和设定的值无法区分
func Dail(c Config) (cn Conn, err error)
配置文件解析
如果是把配置文件解析到go结构体, 那么会产生以下问题:
例如Redis, 如果你使用了配置文件映射到结构体, 那么如果别人使用的是redis.NewConn
, 与你的配置文件冲突, 那么副作用是什么?
// config.yaml
server:
address: xxx
// struct
type Config struct {
Address string
}
func main() {
c := Config {Address: xxx}
// 正常使用
c := Redis.Config {
Address: c.Address
}
// 其它使用
r, _ := &redis.NewConn(c)
c.Addr = "xxx" // 副作用是什么?
}
参数扩展
这样写不知道有多少个的方法
func Dial(network, address, timeout string)
func DialDatabase(network, address, timeout, database string) (Conn, error)
函数可选参数
缺点: 参数不清晰, 如果填多个Config, 哪个生效呢? 有什么副作用?
func Dail(c ...Config) (cn Conn, err error)
指针默认配置
缺点: 使用nil
来表示使用默认的值, 这个nil
作为默认值不是特别好. nil不应该作为公共函数的参数
func Dail(c *Config) (cn Conn, err error)
func main() {
Dail(nil)
}